#include "imgui_impl_dx11.h"
#include "imgui_impl_win32.h"
#include <copi_oswindow.h>
#include <d3d11.h>
#include <imgui.h>
#include <iostream>
#include <libloaderapi.h>
#include <winerror.h>

namespace COPI {
class COPIDx11Manager
{
public:
    ID3D11Device*           device           = nullptr;
    ID3D11DeviceContext*    deviceContext    = nullptr;
    IDXGISwapChain*         swapChain        = nullptr;
    ID3D11RenderTargetView* renderTargetView = nullptr;
    HWND                    hwnd;
    WNDCLASSEXW             wcex;

public:
    COPIDx11Manager( HWND _hwnd ) { hwnd = _hwnd; }
    ~COPIDx11Manager() { hwnd = nullptr; }
    void CreateRenderTarget()
    {
        ID3D11Texture2D* pBackBuffer;
        swapChain->GetBuffer( 0, IID_PPV_ARGS( &pBackBuffer ) );
        device->CreateRenderTargetView( pBackBuffer, nullptr, &renderTargetView );
        pBackBuffer->Release();
    }
    void CleanupRenderTarget()
    {
        if ( renderTargetView ) {
            renderTargetView->Release();
            renderTargetView = nullptr;
        }
    }
    bool CreateDevice3D()
    {
        DXGI_SWAP_CHAIN_DESC sd;
        ZeroMemory( &sd, sizeof( sd ) );
        sd.BufferCount                        = 2;
        sd.BufferDesc.Width                   = 0;
        sd.BufferDesc.Height                  = 0;
        sd.BufferDesc.Format                  = DXGI_FORMAT_R8G8B8A8_UNORM;
        sd.BufferDesc.RefreshRate.Numerator   = 60;
        sd.BufferDesc.RefreshRate.Denominator = 1;
        sd.Flags                              = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
        sd.BufferUsage                        = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        sd.OutputWindow                       = hwnd;
        sd.SampleDesc.Count                   = 1;
        sd.SampleDesc.Quality                 = 0;
        sd.Windowed                           = TRUE;
        sd.SwapEffect                         = DXGI_SWAP_EFFECT_DISCARD;

        UINT createDeviceFlags = 0;
        // createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
        D3D_FEATURE_LEVEL       featureLevel;
        const D3D_FEATURE_LEVEL featureLevelArray [2] = {
            D3D_FEATURE_LEVEL_11_0,
            D3D_FEATURE_LEVEL_10_0,
        };
        HRESULT res = D3D11CreateDeviceAndSwapChain( nullptr,
                                                     D3D_DRIVER_TYPE_HARDWARE,
                                                     nullptr,
                                                     createDeviceFlags,
                                                     featureLevelArray,
                                                     2,
                                                     D3D11_SDK_VERSION,
                                                     &sd,
                                                     &swapChain,
                                                     &device,
                                                     &featureLevel,
                                                     &deviceContext );
        if ( res == DXGI_ERROR_UNSUPPORTED )   // Try high-performance WARP software driver if
                                               // hardware is not available.
            res = D3D11CreateDeviceAndSwapChain( nullptr,
                                                 D3D_DRIVER_TYPE_WARP,
                                                 nullptr,
                                                 createDeviceFlags,
                                                 featureLevelArray,
                                                 2,
                                                 D3D11_SDK_VERSION,
                                                 &sd,
                                                 &swapChain,
                                                 &device,
                                                 &featureLevel,
                                                 &deviceContext );
        if ( res != S_OK ) return false;

        CreateRenderTarget();
        return true;
    }

    void CleanupDevice3D()
    {
        CleanupRenderTarget();
        if ( swapChain ) {
            swapChain->Release();
            swapChain = nullptr;
        }
        if ( deviceContext ) {
            deviceContext->Release();
            deviceContext = nullptr;
        }
        if ( device ) {
            device->Release();
            device = nullptr;
        }
    }
};
}   // namespace COPI

extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler( HWND hWnd, UINT msg, WPARAM wParam,
                                                              LPARAM lParam );
static COPI::COPIDx11Manager* copiDx11Manager = nullptr;
static UINT                   g_ResizeWidth   = 0;
static UINT                   g_ResizeHeight  = 0;

LRESULT WINAPI WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    if ( ImGui_ImplWin32_WndProcHandler( hWnd, msg, wParam, lParam ) ) return true;

    switch ( msg ) {
    case WM_SIZE:
    {
        if ( wParam == SIZE_MINIMIZED ) return 0;
        g_ResizeWidth  = (UINT) LOWORD( lParam );   // Queue resize
        g_ResizeHeight = (UINT) HIWORD( lParam );
        return 0;
    }
    case WM_SYSCOMMAND:
        if ( ( wParam & 0xfff0 ) == SC_KEYMENU )   // Disable ALT application menu
            return 0;
        break;
    case WM_DESTROY: ::PostQuitMessage( 0 ); return 0;
    case WM_KEYDOWN:
        // if ( g_ui != nullptr ) {
        //     // key_callback( int key, int scancode, int action, int mods )
        //     g_ui->globalKeyCallback( get_char( wParam ), 0, 1, 0 );
        // }
        break;
    case WM_KEYUP:
        // if ( g_ui != nullptr ) {
        //     // key_callback( int key, int scancode, int action, int mods )
        //     g_ui->globalKeyCallback( get_char( wParam ), 0, 0, 0 );
        // }
        break;
    }
    return ::DefWindowProcW( hWnd, msg, wParam, lParam );
}

void copi_os_window_open( struct oswindow* windata )
{
    ImGui_ImplWin32_EnableDpiAwareness();

    WNDCLASSEXW wcex   = {};
    wcex.cbSize        = sizeof( WNDCLASSEXW );
    wcex.style         = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc   = WndProc;
    wcex.hInstance     = GetModuleHandle( nullptr );
    wcex.hIcon         = LoadIconW( GetModuleHandle( nullptr ), L"MAIN_ICON" );
    wcex.hIconSm       = nullptr;
    wcex.hCursor       = nullptr;
    wcex.hbrBackground = nullptr;
    wcex.lpszClassName = L"COPI_CLASS_NAME";

    ::RegisterClassExW( &wcex );

    HWND hwnd = ::CreateWindowExW( 0,
                                   wcex.lpszClassName,
                                   L"COPI",
                                   WS_OVERLAPPEDWINDOW,
                                   100,
                                   100,
                                   280,
                                   800,
                                   nullptr,
                                   nullptr,
                                   wcex.hInstance,
                                   nullptr );

    windata->hwnd = hwnd;

    copiDx11Manager       = new COPI::COPIDx11Manager( hwnd );
    copiDx11Manager->wcex = wcex;

    if ( !copiDx11Manager->CreateDevice3D() ) {
        copiDx11Manager->CleanupDevice3D();
        ::UnregisterClassW( wcex.lpszClassName, wcex.hInstance );
        // log_error( "初始化d3d失败" );
        return;
    }
    ShowWindow( hwnd, SW_SHOWDEFAULT );
    UpdateWindow( hwnd );

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();
    (void) io;
    io.Fonts->AddFontFromFileTTF( COPI_DEFAULT_FONT_NAME,
                                  COPI_DEFAULT_FONT_SIZE,
                                  nullptr,
                                  io.Fonts->GetGlyphRangesChineseFull() );
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;

    // ImGui::StyleColorsDark();
    // ImGui::StyleColorsClassic();

    ImGui_ImplWin32_Init( hwnd );
    ImGui_ImplDX11_Init( copiDx11Manager->device, copiDx11Manager->deviceContext );
}

void copi_os_window_close( struct oswindow* windata )
{
    copiDx11Manager->CleanupDevice3D();
    DestroyWindow( windata->hwnd );
    UnregisterClassW( copiDx11Manager->wcex.lpszClassName, copiDx11Manager->wcex.hInstance );
}

void copi_os_window_loop( struct oswindow* windata, loop_callback_t callback )
{
    float MS_PRE_FRAME = (float) 1000 / 60;
    bool  done         = false;
    int w,h = 0;
    while ( !done ) {

        MSG msg;
        while ( ::PeekMessage( &msg, nullptr, 0U, 0U, PM_REMOVE ) ) {
            ::TranslateMessage( &msg );
            ::DispatchMessage( &msg );
            if ( msg.message == WM_QUIT ) done = true;
        }
        if ( done ) break;

        // 重新处理缩放问题
        if ( g_ResizeWidth != 0 && g_ResizeHeight != 0 ) {
            copiDx11Manager->CleanupRenderTarget();
            copiDx11Manager->swapChain->ResizeBuffers(
                0, g_ResizeWidth, g_ResizeHeight, DXGI_FORMAT_UNKNOWN, 0 );
            g_ResizeWidth = g_ResizeHeight = 0;
            copiDx11Manager->CreateRenderTarget();
        }

        ImGui_ImplDX11_NewFrame();
        ImGui_ImplWin32_NewFrame();
        ImGui::NewFrame();

        ImGuiIO& io = ImGui::GetIO();
        w = io.DisplaySize.x;
        h = io.DisplaySize.y;

        if ( callback != NULL ) {
            callback(w, h);
        }

        ImGui::Render();
        copiDx11Manager->deviceContext->OMSetRenderTargets(
            1, &copiDx11Manager->renderTargetView, nullptr );
        copiDx11Manager->deviceContext->ClearRenderTargetView( copiDx11Manager->renderTargetView,
                                                               windata->bg );
        ImGui_ImplDX11_RenderDrawData( ImGui::GetDrawData() );

        if ( copiDx11Manager->swapChain->Present( 1, 0 ) == DXGI_STATUS_OCCLUDED ) {
            Sleep( MS_PRE_FRAME );
        }
    }
}
